package com.katze.common.data;

import com.katze.common.CommonUtils;
import com.katze.common.data.converters.*;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;

@SuppressWarnings("unchecked")
public class DataProperties extends Hashtable<String, Object> {

    private static final Map<Object, Converter<?>> converts = new HashMap<>();

    static {
        converts.put(Date.class, new DateConverter());
        converts.put(Long.class, new LongConverter());
        converts.put(Integer.class, new IntegerConverter());
        converts.put(String.class, new StringConverter());
        converts.put(BigDecimal.class, new BigDecimalConverter());
    }

    public DataProperties(){}

    public DataProperties(Map<String, Object> defaults){
        putAll(defaults);
    }

    public synchronized String setProperty(String key, Object value) {
        return (String) put(key, value);
    }

    public String getProperty(String key) {
        Object sup = super.get(key);
        return (sup instanceof String) ? (String) sup : null;
    }

    public String getProperty(String key, String defaultValue) {
        return Optional.ofNullable(getProperty(key)).orElse(defaultValue);
    }

    public <T>T getProperty(String key, Function<String, T> function){
        return function.apply(getProperty(key));
    }

    public void getProperty(String key, Consumer<String> consumer){
        String value = getProperty(key);
        if (value != null){
            consumer.accept(value);
        }
    }

    public <T, R, L> T getProperty(LambdaFieldGetter<L, R> lambda, Class<T> cs) {
        return converter(this.get(CommonUtils.getLambdaFieldName(lambda)), cs);
    }

    public <T, R, L> T getProperty(LambdaFieldGetter<L, R> lambda, T defaultValue, Class<T> cs) {
        return converter(this.getOrDefault(CommonUtils.getLambdaFieldName(lambda), defaultValue), cs);
    }

    public <T>T toInstance(Class<T> cs) throws ReflectiveOperationException {
        T instance = cs.getDeclaredConstructor().newInstance();
        for (Map.Entry<String, Object> entry : this.entrySet()) {
            try {
                Field field = cs.getDeclaredField(entry.getKey());
                field.setAccessible(true);
                field.set(instance, entry.getValue());
            } catch (NoSuchFieldException | IllegalAccessException e) {
                throw new ReflectiveOperationException(e);
            }
        }
        return instance;
    }

    public static <T>T converter(Object value, Class<T> cs) {
        Converter<T> converter = (Converter<T>) converts.get(cs);
        if (converter == null) {
            throw new NullPointerException(cs.getSimpleName() + " type not currently supported");
        }
        return converter.format(value);
    }
}
